package main;


import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;

import javax.sound.midi.*;

public class CSVtoMidiConverter
{
	private static final String midiCSVfile = "csvMidi.csv";
	private static final String outFile = "outFile.MID";
	
	private static final String timingRes = "Timing Resolution";
	private static final String instrument = "Instrument";
	private static final String data = "Data";
	
	// array constants
	private static final int TICK = 0;
	private static final int NOTE = 1;
	private static final int VOLUME = 2;
	
	// midi constants
	private int timingResolution;
	private int instrumentNum;
	private int volume;
	private int channel;
	
	// midi stuff
	private Sequence sequence;
	private Track[] track;
	private final ArrayList<String[]> midiInfo;
	
	public CSVtoMidiConverter() throws IOException
	{
		this.channel = 0;  // by default for this file type
		this.midiInfo = new ArrayList<String[]>();
		readMidiData();
	}
	
	/* =========================================================================
	 * MIDI File Construction
	 * =========================================================================
	 */
	public void createMidi() throws InvalidMidiDataException, IOException
	{
		// this initializes the midi sequencer to a tempo-based timing type, 
		// for which the resolution is expressed in pulses (ticks) per 
		// quarter note.
		sequence = new Sequence(Sequence.PPQ, timingResolution);
		
		// Initialize Track -- single voice
		track = new Track[1];
		track[channel]=sequence.createTrack();
		
		// This uses IBM's Midi MessageInfo class
		ShortMessage shortMessage = new ShortMessage();
		
		// This puts our instrument (channel 0) into the track
		shortMessage.setMessage(ShortMessage.PROGRAM_CHANGE, 0, 0, 0 );
		track[channel].add(new MidiEvent(shortMessage, 0));
		
		Iterator<String[]> i = midiInfo.iterator();
		while( i.hasNext() )
		{
			// each array has a specific array setup
			String[] arr = i.next();
			int tick, note, volume;
			tick = Integer.parseInt(arr[TICK]);
			note = Integer.parseInt(arr[NOTE]);
			volume = Integer.parseInt(arr[VOLUME]);
			
			MidiEvent midiEvent = createNoteOnEvent(note, tick, channel, volume);
			track[channel].add(midiEvent);
		}
		outputTrackInfo();
		writeMidiFile();
	}
	
	private void outputTrackInfo()
	{
		System.out.println();
		if (track != null)
		{
			System.out.println("Track 0: "); // only one track

			for (int i = 0; i < track[0].size(); ++i)
			{
				MidiEvent event = track[0].get(i);
				System.out.println(" tick " + event.getTick() + ", "
						+ MessageInfo.toString(event.getMessage()));
			}
		}
	}
	
	private void writeMidiFile() throws IOException
	{
		// sequence, single track (0), and the outputfile
		MidiSystem.write(sequence, 0, new File(outFile));
	}
	
	/* =========================================================================
	 * MIDI Event Construction
	 * =========================================================================
	 */
	
	// creates a NOTE event -- turn ON
	private static MidiEvent createNoteOnEvent(int nKey, long tick, int channel, 
			int volume ) 
			throws InvalidMidiDataException
	{
		return createNoteEvent(ShortMessage.NOTE_ON, nKey, volume, tick, channel);
	}
	
	// creates a NOTE event -- turn OFF
//	private static MidiEvent createNoteOffEvent(int nKey, long tick, 
//			int channel, int volume ) 
//			throws InvalidMidiDataException
//	{
//		return createNoteEvent(ShortMessage.NOTE_OFF, nKey, volume, tick, channel);
//	}
	
	// creates a NOTE to turn on or off
	private static MidiEvent createNoteEvent(int shortMessageCommand, int nKey, 
			int volume, long tick, int channel) 
			throws InvalidMidiDataException
	{
		ShortMessage shortMessage = new ShortMessage();
		shortMessage.setMessage(shortMessageCommand, channel, nKey, volume );
		MidiEvent midiEvent = new MidiEvent( shortMessage, tick );
		return midiEvent;
	}
	
	/* =========================================================================
	 * Read data in from CSV file
	 */
	private void readMidiData() throws IOException
	{
		FileInputStream input = new FileInputStream(midiCSVfile);
		BufferedReader reader = new BufferedReader(new InputStreamReader(input));
		
		// We expect a certain format for the first lines:
		timingResolution = setTimingResolution(reader.readLine());
		instrumentNum = setInstrumentNum(reader.readLine());
		if( isDataSection(reader.readLine()))
		{
			String s = "";
			while ((s = reader.readLine()) != null)
			{
				if (s != "")
				{
					midiInfo.add(s.split(","));
				}
			}
		}
		reader.close();
		input.close();
	}
	
	/* =========================================================================
	 * Utility Methods
	 */
	private int setTimingResolution(String s)
	{
		String[] arr = s.split(":");
		if( arr[0].equals(timingRes) )
		{
			return Integer.parseInt(arr[1]);
		}
		else
			return 4; // just a random default
	}
	
	private int setInstrumentNum(String s)
	{
		String[] arr = s.split(":");
		if( arr[0].equals(instrument) )
		{
			return Integer.parseInt(arr[1]);
		}
		else
			return 106; // just a random default
		
	}
	
	private boolean isDataSection(String s) 
	{
		return s.startsWith(data);
	}
}
